use crate::expr::Expr;
use std::mem::swap;
pub struct TokStr {
    peek_item: Option<Expr>, // 用于保存下一个元素
    cur: usize,              // 指向字符串当前解析到的位置
    ch: String,              // 输入字符串
}
const OP: [&str; 8] = ["+", "-", "*", "/", "^", "(", ")", ","];
fn get_oper(code: &str) -> Expr {
    match code {
        "+" => Expr::Add(None, None),
        "-" => Expr::Sub(None, None),
        "*" => Expr::Mul(None, None),
        "/" => Expr::Div(None, None),
        "^" => Expr::Pow(None, None),
        "," => Expr::Comma,
        "(" => Expr::Bra,
        ")" => Expr::Ket,
        &_ => panic!("Not allowed operator"),
    }
}

impl Iterator for TokStr {
    type Item = Expr;
    fn next(&mut self) -> Option<Expr> {
        if self.peek_item.is_some() {
            // exchange
            let mut tmp = None;
            swap(&mut tmp, &mut self.peek_item);
            return tmp;
        }
        // check over bound
        if self.cur >= self.ch.len() {
            return None;
        };
        //check operator
        let res = OP
            .iter()
            .filter(|&x| self.ch[self.cur..].starts_with(x))
            .max()
            .unwrap_or(&"");
        if !res.is_empty() {
            self.cur += res.len();
            return Some(get_oper(res));
        } else {
            // or alpha, number
            let tok = self.ch[self.cur..].chars().next().unwrap();
            if tok.is_alphabetic() {
                let res = Self::take_ident(&self.ch[self.cur..]);
                self.cur += res.len();
                // variable
                match res.as_str() {
                    // 常数
                    "e" | "E" => return Some(Expr::Cstf(std::f32::consts::E)),
                    "pi" | "PI" => return Some(Expr::Cstf(std::f32::consts::PI)),
                    // 三角
                    "sin" => return Some(Expr::Sin(None)),
                    "cos" => return Some(Expr::Cos(None)),
                    "tan" => return Some(Expr::Tan(None)),
                    // 记得 apply
                    "sec" => return Some(Expr::Sec(None)),
                    "csc" => return Some(Expr::Csc(None)),
                    "cot" => return Some(Expr::Cot(None)),
                    // 指数对数
                    "exp" => {
                        return Some(Expr::Pow(Some(Box::new(Expr::Var("e".to_string()))), None))
                    }
                    "log" => return Some(Expr::Log(None, None)),
                    // 反三角
                    "arcsin" => return Some(Expr::ArcSin(None)),
                    "arccos" => return Some(Expr::ArcCos(None)),
                    "arctan" => return Some(Expr::ArcTan(None)),
                    // 不开放自定义函数，省下一步查表操作
                    _ => return Some(Expr::Var(res)),
                }
            } else if tok.is_ascii_digit() {
                // constant value
                let res = Self::take_number(&self.ch[self.cur..]);
                self.cur += res.len();
                if res.contains('.') {
                    return Some(Expr::Cstf(
                        res.parse::<f32>().expect("Not allowed more than one dot"),
                    ));
                } else {
                    return Some(Expr::Csti(res.parse::<usize>().unwrap()));
                }
            }
        }
        None
    }
}

impl TokStr {
    pub fn new(ch: String) -> Self {
        Self {
            peek_item: None,
            ch,
            cur: 0,
        }
    }
    pub fn peek(&mut self) -> Option<Expr> {
        if self.peek_item.is_none() {
            self.peek_item = self.next();
            self.peek_item.clone()
        } else {
            self.peek_item.clone()
        }
    }

    fn take_ident(code: &str) -> String {
        code.chars()
            .take_while(|c| c.is_alphabetic())
            .collect::<String>()
    }
    // TODO Not support float // may add the operator point
    fn take_number(code: &str) -> String {
        code.chars()
            .take_while(|&c| c.is_ascii_digit() || c == '.')
            .collect::<String>()
    }
}
